﻿using System.Collections.Immutable;
using DimensionsHelper.LanguageService.Syntax;

namespace DimensionsHelper.LanguageService.Symbols;

public interface ITypeSymbol : ISymbol
{
    /// <summary>
    ///     获取类型符号的类型类别
    /// </summary>
    TypeKinds TypeKind { get; }


    /// <summary>
    ///     获取基类型符号
    /// </summary>
    ITypeSymbol? BaseType { get; }


    /// <summary>
    ///     获取继承或实现的所有接口
    /// </summary>
    IReadOnlyList<ITypeSymbol> Interfaces { get; }


    /// <summary>
    ///     获取声明的语法节点，如果此符号允许重复声明，
    ///     此属性返回第一个。
    /// </summary>
    IDeclaration? Declaration { get; }


    /// <summary>
    ///     如果此符号定义在模块中，返回模块全名
    /// </summary>
    string? ContainingModuleName { get; }


    /// <summary>
    ///     如果此符号定义在模块中，返回模块符号
    /// </summary>
    ISymbol? ContainingModule { get; }


    /// <summary>
    ///     如果此符号是联合符号，返回<see langword="true" />
    /// </summary>
    bool IsUnion { get; }


    /// <summary>
    ///     如果此符号是模块，返回<see langword="true" />
    /// </summary>
    bool IsModule { get; }


    /// <summary>
    ///     如果此符号是类定义，返回<see langword="true" />
    /// </summary>
    bool IsClass { get; }


    /// <summary>
    ///     如果此符号是接口定义，返回<see langword="true" />
    /// </summary>
    bool IsInterface { get; }


    /// <summary>
    ///     如果此符号是枚举定义，返回<see langword="true" />
    /// </summary>
    bool IsEnum { get; }


    /// <summary>
    ///     如果符号声明带有 Default 修饰符，返回<see langword="true" />
    /// </summary>
    bool IsDefault { get; }

    /// <summary>
    ///     如果符号带有 ReadOnly 修饰符或者是只读的本地变量，返回<see langword="true" />
    /// </summary>
    bool IsReadOnly { get; }

    /// <summary>
    ///     如果符号带有 WriteOnly 修饰符，返回<see langword="true" />
    /// </summary>
    bool IsWriteOnly { get; }

    /// <summary>
    ///     如果符号是通过签名节点声明的，返回<see langword="true" />
    /// </summary>
    bool IsSignature { get; }


    /// <summary>
    ///     如果符号带有绑定类型，返回<see langword="true" />
    /// </summary>
    bool IsBindable { get; }


    /// <summary>
    ///     如果符号带有参数表，返回<see langword="true" />
    /// </summary>
    bool WithParameter { get; }


    /// <summary>
    ///     如果符号内带有常量声明或定义，返回<see langword="true" />
    /// </summary>
    bool WithConstant { get; }


    /// <summary>
    ///     此符号是否允许生成数组符号
    /// </summary>
    bool CanMakeArray { get; }


    /// <summary>
    ///     获取成员符号列表
    /// </summary>
    ImmutableArray<ISymbol> GetMembers();


    /// <summary>
    ///     获取命名成员的符号，不区分大小写，如果未找到，返回<see langword="null" />
    /// </summary>
    ITypeSymbol? LookupNamedMember(string name);


    /// <summary>
    ///     获取默认属性
    /// </summary>
    /// <returns></returns>
    ITypeSymbol? GetDefaultProperty();


    /// <summary>
    ///     获取默认方法
    /// </summary>
    ITypeSymbol? GetDefaultMethod();


    /// <summary>
    ///     获取索引器方法
    /// </summary>
    ITypeSymbol? GetIndexer();


    /// <summary>
    ///     如果给定的类型符号可以赋值给当前符号，返回<see langword="true" />
    /// </summary>
    bool IsAssignableFrom(ITypeSymbol type);


    /// <summary>
    ///     如果当前符号继承或实现了给定接口，返回<see langword="true" />
    /// </summary>
    bool HasInterface(ITypeSymbol symbol);


    /// <summary>
    ///     创建以此符号为元素的数组符号
    /// </summary>
    /// <param name="rank">数组维度数</param>
    /// <param name="boundaries">数组各维度的长度，可以忽略，忽略的单个维度长度设置为-1</param>
    /// <returns></returns>
    ITypeSymbol MakeArrayType(int rank, int[]? boundaries);
}

public static class TypeSymbolExtension
{
    /// <summary>
    ///     如果当前符号是<see cref="IBindableSymbol" />，返回<see cref="IBindableSymbol.BindingSymbol" />，
    ///     此方法不会继续向下查找默认属性。
    /// </summary>
    public static ITypeSymbol GetBindableBindingOrThis(this ITypeSymbol typeSymbol)
    {
        return typeSymbol.IsBindable ? ((IBindableSymbol)typeSymbol).BindingSymbol : typeSymbol;
    }


    /// <summary>
    ///     获取可能是<see cref="IBindableSymbol" />类型符号的最终绑定符号。
    /// </summary>
    public static ITypeSymbol GetFinalBindableBindingOrThis(this ITypeSymbol typeSymbol)
    {
        ITypeSymbol symbol = typeSymbol;

        while (symbol.IsBindable)
        {
            symbol = ((IBindableSymbol)symbol).BindingSymbol;
        }

        return symbol;
    }


    /// <summary>
    ///     查找类型的默认属性链上的指定名称成员符号
    /// </summary>
    public static ITypeSymbol? GetNamedMember(this ITypeSymbol typeSymbol, string name)
    {
        ITypeSymbol? symbol = typeSymbol;

        while (symbol != null)
        {
            ITypeSymbol? member = symbol.LookupNamedMember(name);

            if (member != null)
            {
                return member;
            }

            symbol = symbol.GetDefaultProperty()?.GetBindableBindingOrThis();
        }

        return null;
    }


    /// <summary>
    ///     查找类型的默认属性链上的集合索引器，一般情况下，索引器属性带有Default修饰符
    /// </summary>
    public static ITypeSymbol? LookupIndexer(this ITypeSymbol typeSymbol)
    {
        ITypeSymbol symbol = typeSymbol;

        while (true)
        {
            if (symbol.IsBindable)
            {
                symbol = ((IBindableSymbol)symbol).BindingSymbol;
            }

            // Item()
            ITypeSymbol? indexer = symbol.GetIndexer();
            if (indexer != null)
            {
                return indexer;
            }

            ITypeSymbol? defaultProperty = symbol.GetDefaultProperty();
            if (defaultProperty == null)
            {
                break;
            }

            symbol = defaultProperty;
        }

        return null;
    }


    /// <summary>
    ///     查找类型符号或其默认属性链上的索引器。查找到的记过可能是带有<see cref="ArrayTypeSymbol" />绑定的类型、
    ///     带有参数的<see cref="PropertySymbol" />或<see cref="ValueTypeSymbol.Categorical" />。
    /// </summary>
    /// <param name="typeSymbol">当前符号</param>
    /// <returns>查找到的索引器，如果未找到，返回<see langword="null" /></returns>
    public static ITypeSymbol? LookupIndexerOrPropertyWithParameters(this ITypeSymbol typeSymbol)
    {
        ITypeSymbol symbol = typeSymbol;

        while (true)
        {
            // Property
            if (symbol.IsProperty)
            {
                PropertySymbol prop = (PropertySymbol)symbol;
                // has parameter
                if (prop.HasParameter)
                {
                    return prop;
                }

                symbol = prop.BindingSymbol;
                continue;
            }

            // Bindable
            if (symbol.IsBindable)
            {
                symbol = ((IBindableSymbol)symbol).BindingSymbol;
            }

            // ArrayType
            if (symbol.IsArrayType)
            {
                return symbol;
            }

            // Categorical
            if (ReferenceEquals(symbol, ValueTypeSymbol.Categorical))
            {
                return symbol;
            }

            // Item()
            ITypeSymbol? indexer = symbol.GetIndexer();
            if (indexer != null)
            {
                return indexer;
            }

            // DefaultProperty
            ITypeSymbol? defaultProperty = symbol.GetDefaultProperty();
            if (defaultProperty == null)
            {
                break;
            }

            symbol = defaultProperty;
        }

        return null;
    }


    /// <summary>
    ///     从当前类型开始，一直查找到最后一个带有不可忽略参数的默认属性。如果没有默认属性，返回当前类型符号。
    ///     注意，此方法查找的是绑定的类型，如：Default Property Item(Index As Long) As String 中的 String。
    ///     当前类型符号需要是<see cref="NamedTypeSymbol" />。
    /// </summary>
    public static ITypeSymbol GetFinalDefaultPropertyBindingOrThis(this ITypeSymbol typeSymbol)
    {
        ITypeSymbol symbol = typeSymbol.GetFinalBindableBindingOrThis();

        while (symbol is { IsObjectType: true })
        {
            NamedTypeSymbol namedType = (NamedTypeSymbol)symbol;
            ITypeSymbol? defaultProperty = namedType.GetDefaultProperty();
            if (defaultProperty is not PropertySymbol property)
            {
                break;
            }

            // 如果默认属性带有不可忽略的参数，跳出
            if (property.Parameters.Any(param => !param.IsOptional))
            {
                break;
            }

            symbol = property.GetBindingSymbol();
        }

        return symbol;
    }
}